#include "StdAfx.h"
#include "GraphCtrl.h"
#include "pugixml/pugixml.hpp"
#include <algorithm>
#include <math.h>
#include <iterator>

enum ECmdMenu
{
    ID_SELECT_START,
    ID_SELECT_END,
    ID_CLEAR,
    ID_MAX_CMD
};

BEGIN_MESSAGE_MAP(GraphCtrl,CWnd)
    ON_WM_PAINT()  
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
    ON_WM_RBUTTONUP()
    ON_COMMAND_RANGE(ID_SELECT_START,ID_MAX_CMD-1,OnContextCmd)
END_MESSAGE_MAP()

GraphCtrl::GraphCtrl(void)
	:_pTableView(NULL)
{
   _commandmap[ID_CLEAR] = &GraphCtrl::ClearSelection;
}

GraphCtrl::~GraphCtrl(void)
{
}

void GraphCtrl::OnContextCmd(UINT uID)
{

    method fnc = _commandmap[uID];
    if(fnc) {
        (this->*fnc)();
        return;
    }

    // unknown command in context menu
    ASSERT(false);
}

bool GraphCtrl::CreateFromStatic( UINT nStaticID, CWnd* pParent )
{
    CStatic  wndStatic;		

    if (pParent == NULL || !wndStatic.SubclassDlgItem(nStaticID, pParent))
    {
        return false;
    }
	

	/* Get the static windows rectangle and transform it into
	parent client coordinates, instead of screen coordinates */

	CRect    rectStatic;

	wndStatic.GetWindowRect(&rectStatic);
	pParent->ScreenToClient(&rectStatic);

		
	if ( !CreateEx( wndStatic.GetExStyle(), 
		            NULL, NULL, WS_CHILD | WS_VISIBLE | wndStatic.GetStyle(),
		            rectStatic, pParent, nStaticID, NULL))
	{		
		wndStatic.DestroyWindow();
		return false;
	}

    return true;
}
void GraphCtrl::OnPaint()
{
    CPaintDC paintDC(this);

    CDC &dc = InitBackBuffer(paintDC);

    CRect &rectClient= getClientRect();
    dc.IntersectClipRect(&rectClient);
    dc.FillSolidRect(&rectClient,RGB(255,255,255));

    if (_graph.size() > 0)
    {
        for (auto it = _graph.begin(); it != _graph.end(); ++it)
        {
            dc.Ellipse(ComputeRect(it->first));

            if (it->second.size() > 1)
            {
                auto itStartVertex = it->second.begin();
                auto itEndVertex = itStartVertex;
                for (++itEndVertex; itEndVertex != it->second.end();++itEndVertex)
                {
                    DrawVertex(dc,*itStartVertex,*itEndVertex);
                }
            }
        }
    }
  
    DrawInfoRect(dc);
    ReleaseBackBuffer(paintDC);
}

CDC& GraphCtrl::InitBackBuffer(CPaintDC &dc)
{
    /* Create the offscreen DC and associated bitmap */

    _dcBack.CreateCompatibleDC(&dc);
    _bmBack.CreateCompatibleBitmap(&dc, getClientRect().Width(),
        getClientRect().Height());

    _pOldBitmap = _dcBack.SelectObject(&_bmBack);
    return _dcBack;
}

void GraphCtrl::ReleaseBackBuffer(CPaintDC &dc)
{
	/* Copy the offscreen buffer to the onscreen CPaintDC.
    Then free the back buffer objects. */

    dc.BitBlt(getClientRect().left, getClientRect().top, 
        getClientRect().Width(),  getClientRect().Height(),
        &_dcBack, getClientRect().left, getClientRect().top,
        SRCCOPY);

    dc.SelectObject(_pOldBitmap);

    _bmBack.DeleteObject();
    _dcBack.DeleteDC();
}

CRect& GraphCtrl::getClientRect()
{
    static CRect rectClient;
    return (GetClientRect(&rectClient), rectClient);
}

void GraphCtrl::AddStation( Station* connectionStation,Station* stationToAdd)
{
    Stations stationLst = _graph[connectionStation];
    if (connectionStation != stationToAdd && stationLst.size() == 0)
        throw 0;

    stationLst.push_back(stationToAdd);
    _graph[connectionStation] = stationLst;
	_pTableView->AddStation(stationToAdd);

    //update extremes coords
    _minmaxCoord.Update(stationToAdd->latitude,stationToAdd->longitude);
}

void GraphCtrl::AddStation(Station* connectionStation,list<Station*>& stations)
{
    Stations stationLst = _graph[connectionStation];
    std::copy(stations.begin(),stations.end(),std::back_inserter(stationLst));
    _graph[connectionStation] = stationLst;
}

void GraphCtrl::AddTableView(CTableView* pTableView)
{
	_pTableView = pTableView;
}

CRect GraphCtrl::ComputeRect(const Station* pStation)
{
    CRect rect;
	CRect& clientRect = getClientRect();
	clientRect.DeflateRect(2*NODE_RADIUS,2*NODE_RADIUS);

    double deltaX = (double)pStation->longitude-_minmaxCoord.left;
    double deltaY = (double)pStation->latitude-_minmaxCoord.top;
    rect.left = static_cast<LONG>(deltaX*(double)clientRect.right/(_minmaxCoord.right - _minmaxCoord.left));
    rect.top = static_cast<LONG>(clientRect.bottom - deltaY*(double)clientRect.bottom/(_minmaxCoord.bottom - _minmaxCoord.top));

	rect.right = rect.left + 2*NODE_RADIUS;
	rect.bottom = rect.top + 2*NODE_RADIUS;		

    return rect;
}

void GraphCtrl::DrawVertex( CDC &dc ,const Station* startStation, const Station* endStation )
{  

    CRect startRect = ComputeRect(startStation);
    CRect endRect  = ComputeRect(endStation);
    
    CPoint startPoint;
    CPoint endPoint;

    IntersectWithNode(startRect,endRect,startPoint,endPoint);
    
    
    
    CPen linePen,*pOldPen = NULL;
    if (wcscmp(startStation->line,endStation->line) == 0)
    {
        linePen.CreatePen(PS_SOLID,2,_lineDrawSettings[startStation->line]);
        pOldPen = dc.SelectObject(&linePen);
    }
    dc.MoveTo(startPoint);
    dc.LineTo(endPoint);
    if(pOldPen)
    {
        dc.SelectObject(pOldPen);
        linePen.DeleteObject();
    }
}

void GraphCtrl::AddLink(const wchar_t* startStation,const wchar_t* startLine,const wchar_t* endStation, const wchar_t* endLine)
{

    StationIdentifier pStartStation = NULL;
    StationIdentifier pEndStation = NULL;
    bool bStartStation = false, bEndStation = false;
    
    for (auto it = _graph.begin();(!bStartStation || !bEndStation) && it!= _graph.end();++it)
    {
        if (!bStartStation && wcscmp(it->first->name,startStation) == 0 && wcscmp(it->first->line,startLine) == 0)
        {
            pStartStation = it->first;
            bStartStation = true;
        }
        else
        if (!bEndStation && wcscmp(it->first->name,endStation) == 0 && wcscmp(it->first->line,endLine) == 0)
        {
            pEndStation = it->first;
            bEndStation = true;
        }
    }

    if (pStartStation && pEndStation)
    {
        Stations stationLst = _graph[pStartStation];
        stationLst.push_back(const_cast<Station*>(pEndStation));
        _graph[pStartStation] = stationLst;
    }
}

void GraphCtrl::AddLink(const int id1, const int id2)
{
    StationIdentifier pStartStation = NULL;
    StationIdentifier pEndStation = NULL;
    bool bStartStation = false, bEndStation = false;

    for (auto it = _graph.begin();(!bStartStation || !bEndStation) && it!= _graph.end();++it)
    {
        if (!bStartStation && it->first->id == id1)
        {
            pStartStation = it->first;
            bStartStation = true;
        }
        else
            if (!bEndStation && it->first->id == id2)
            {
                pEndStation = it->first;
                bEndStation = true;
            }
    }

    if (pStartStation && pEndStation)
    {
        Stations stationLst = _graph[pStartStation];
        stationLst.push_back(const_cast<Station*>(pEndStation));
        _graph[pStartStation] = stationLst;
    }
}

void GraphCtrl::IntersectWithNode(const CRect& rect1, const CRect& rect2,CPoint &p1, CPoint &p2)
{   
    CPoint c1 = rect1.CenterPoint();
    CPoint c2 = rect2.CenterPoint()-c1;

    int r1 = rect1.Width()/2;
    int r2 = rect2.Width()/2;

    if (c2.x == 0)
    {
        p1.x = c1.x;
        p1.y = c1.y+(c2.y < 0? r1*(-1): r1); 
        p2.x = c2.x+c1.x;
        p2.y = (c2.y+c1.y) +(0 < c2.y ? r1*(-1): r1);
        return;
    }
 
    double m = static_cast<double>(c2.y)/c2.x;
    
    double deltaX =  sqrt(((double)r1*r1)/(1+m*m));
    
    if (c2.x +c1.x > c1.x)
    {
        p1.x = static_cast<int>(deltaX+c1.x);
        p1.y = static_cast<int>(m*deltaX+c1.y);
        p2.x = static_cast<LONG>(-deltaX+ rect2.CenterPoint().x);
        p2.y = static_cast<int>(m*(-deltaX) +rect2.CenterPoint().y);
    }
    else
    {
        p1.x = static_cast<int>(-deltaX+c1.x);
        p1.y = static_cast<int>(m*(-deltaX)+c1.y);
        p2.x = static_cast<int>(deltaX+ rect2.CenterPoint().x);
        p2.y = static_cast<int>(m*deltaX +rect2.CenterPoint().y);
    }
}

const Station* GraphCtrl::GetStation(const CPoint& point)
{
	for (auto it = _graph.begin(); it != _graph.end(); ++it)
	{
		CRect stationRect = ComputeRect(it->first);
		if (stationRect.PtInRect(point))
			return it->first;
	}

	return NULL;
}

void GraphCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	const Station* pStation = GetStation(point);
	if (pStation && (nFlags & MK_LBUTTON))
    {
        if (nFlags & MK_CONTROL)
		    _pTableView->Select(pStation,true);
        else
            _pTableView->Select(pStation,false);
    }
}

void GraphCtrl::OnRButtonUp(UINT nFlags, CPoint point)
{
    ClientToScreen(&point);
    DisplayContextMenu(point);
    CWnd::OnRButtonUp(nFlags,point);
}

void GraphCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
    const Station* pStation = GetStation(point);
    if (pStation)
    {
        _infoRect.clear();
        _infoRect.insert(pStation);
        InvalidateRect(NULL);
    }
    else
    {
        _infoRect.clear();
        InvalidateRect(NULL);
    }
}

void GraphCtrl::DrawInfoRect( CDC &dc )
{
    CRect& clientRect = getClientRect();
    vector<CString> infoLines;
    CString strName,strLine,strLong,strLat;
    for (auto it = _infoRect.begin(); it != _infoRect.end();++it)
    {
        strName.Format(_T("Station: %s"),(*it)->name);
        infoLines.push_back(strName);
        strLine.Format(_T("Line: %s"),(*it)->line);
        infoLines.push_back(strLine);
        strLat.Format(_T("Lat: %3.6f"),(*it)->latitude);
        infoLines.push_back(strLat);
        strLong.Format(_T("Long: %3.6f"),(*it)->longitude);
        infoLines.push_back(strLong);
        strLong.Format(_T("Id: %i"),(*it)->id);
        infoLines.push_back(strLong);
        int maxExtent = 0;
        int height =4 ;
        for (unsigned int i = 0;i < infoLines.size(); ++i)
        {
            CSize textExtent = dc.GetTextExtent(infoLines.at(i));
            maxExtent = std::max<int>(maxExtent,textExtent.cx);
            height += textExtent.cy+2;
        }
        maxExtent+= 4;
        CRect infoRect = ComputeRect(*it);
        infoRect.MoveToXY(infoRect.left+infoRect.Width(),infoRect.bottom-infoRect.Height()/2);
        infoRect.right = infoRect.left + maxExtent;
        infoRect.bottom = infoRect.top + height;
        if (infoRect.right > clientRect.right)
            infoRect.MoveToX(infoRect.left-maxExtent - 2*NODE_RADIUS);
        if (infoRect.bottom > clientRect.bottom)
            infoRect.MoveToY(infoRect.top - height);

        dc.RoundRect(infoRect,CPoint(5,5));
        CRgn rgn;
        rgn.CreateRoundRectRgn(infoRect.left,infoRect.top,infoRect.right,infoRect.bottom,5,5);
        CBrush brush;
        brush.CreateSolidBrush(RGB(255,236,170));
        dc.FillRgn(&rgn,&brush);
        COLORREF color = dc.GetBkColor();
        dc.SetBkColor(RGB(255,236,170));
        int y = infoRect.top  +2;
        for (auto itInfo = infoLines.begin(); itInfo != infoLines.end();++itInfo)
        {
            dc.TextOut(infoRect.left +2 ,y,*itInfo,itInfo->GetLength());
            y += dc.GetTextExtent(*itInfo).cy + 2;
        }
        dc.SetBkColor(color);
    }
}


void GraphCtrl::Load(const wchar_t* szFilePath)
{
    pugi::xml_document xmlDoc;
    pugi::xml_parse_result xmlStations = xmlDoc.load_file(szFilePath);
    
    pugi::xpath_node_set stations = xmlDoc.select_nodes(_T("StationList/InfoList/station"));
    for (auto it = stations.begin(); it != stations.end(); ++it)
    {
        pugi::xpath_node nodeStation = *it;
        const wchar_t* szName = nodeStation.node().attribute(_T("name")).value();
        const wchar_t* szLine = nodeStation.node().attribute(_T("line")).value();
        double dLatitude = nodeStation.node().attribute(_T("latitude")).as_float();
        double dLongitude = nodeStation.node().attribute(_T("longitude")).as_float();
        int iId = nodeStation.node().attribute(_T("id")).as_int();
        Station* pNewStation = new Station(dLongitude,dLatitude,szName,szLine,iId);
        AddStation(pNewStation,pNewStation);
    }  

    pugi::xpath_node_set links = xmlDoc.select_nodes(_T("StationList/LinkList/Link"));
    for (auto it = links.begin(); it != links.end(); ++it)
    {
        Station* pFirst = GetStation((*it).node().attribute(_T("id")).as_int());
        if (pFirst)
        {
            pugi::xpath_node_set idSet = (*it).node().select_nodes(_T("Id"));
            list<Station*> stationLst;
            for (auto itId = idSet.begin(); itId != idSet.end(); ++itId)
            {
                pugi::xpath_node nodeLink = *itId;
                Station* pSecond = GetStation(nodeLink.node().attribute(_T("value")).as_int());
                stationLst.push_back(pSecond);
                _minmaxCoord.Update(pSecond->latitude,pSecond->longitude);
            }
            AddStation(pFirst,stationLst);
        }
    }

    pugi::xpath_node_set lineSettings = xmlDoc.select_nodes(_T("StationList/DrawInfo/Line"));
    for (auto it = lineSettings.begin(); it != lineSettings.end(); ++it)
    {
        wstring line = it->node().attribute(_T("name")).value();
        unsigned int color = it->node().attribute(_T("color")).as_uint();
        _lineDrawSettings[line] = color;
    }
    InvalidateRect(NULL);
	_pTableView->RedrawWindow();
}

Station* GraphCtrl::GetStation(const int id)
{    
    for (auto it = _graph.begin(); it != _graph.end(); ++it)
        if (it->first->id == id)
            return it->first;
    return NULL;
}   

void GraphCtrl::DisplayContextMenu(CPoint& point)
{
    CPoint pt(point);
    ScreenToClient(&pt);
    CMenu menu;
    VERIFY(menu.CreatePopupMenu());
    VERIFY(menu.AppendMenu(MF_STRING,ID_SELECT_START,_T("Set Start")));
    VERIFY(menu.AppendMenu(MF_STRING,ID_SELECT_END,_T("Set End")));
    VERIFY(menu.AppendMenu(MF_STRING,ID_CLEAR,_T("Clear Selection")));

    menu.TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this);
}

void GraphCtrl::ClearSelection()
{
    _pTableView->DeselectAll();
}